﻿using DotNetCommon.Extensions;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace DotNetCommon.Accessors;
/// <summary>
/// 快速访问给定类型的对象的属性值, 参照: <seealso cref="Accessor"/>
/// </summary>
/// <typeparam name="TInstance"></typeparam>
public sealed class GenericAccessor<TInstance> : ObjectAccessor where TInstance : class
{
    private readonly Hashtable
        _genericInstanceGettersCache,
        _genericInstanceSettersCache,
        _genericPropertiesGettersCache,
        _genericPropertiesSettersCache;
    private readonly Hashtable
        _genericFieldsGettersCache,
        _genericFieldsSettersCache;

    [DebuggerStepThrough]
    internal GenericAccessor(bool ignoreCase, bool includeNonPublic)
        : base(typeof(TInstance), ignoreCase, includeNonPublic)
    {
        //属性
        _genericPropertiesGettersCache = new Hashtable(Properties.Count, Comparer);
        _genericPropertiesSettersCache = new Hashtable(Properties.Count, Comparer);
        _genericInstanceGettersCache = new Hashtable(Properties.Count, Comparer);
        _genericInstanceSettersCache = new Hashtable(Properties.Count, Comparer);

        foreach (var pair in Properties)
        {
            var propName = pair.Key;
            var prop = pair.Value;

            if (prop.CanRead)
            {
                _genericPropertiesGettersCache[propName] = AccessorBuilder.BuildGetter<TInstance>(prop, IncludesNonPublic);
            }

            if (prop.CanWrite)
            {
                _genericPropertiesSettersCache[propName] = AccessorBuilder.BuildSetter<TInstance>(prop, IncludesNonPublic);
            }
        }

        //字段
        _genericFieldsGettersCache = new Hashtable(Fields.Count, Comparer);
        _genericFieldsSettersCache = new Hashtable(Fields.Count, Comparer);

        foreach (var pair in Fields)
        {
            var fieldName = pair.Key;
            var field = pair.Value;

            _genericFieldsGettersCache[fieldName] = AccessorBuilder.BuildGetter<TInstance>(field);

            if (!field.IsInitOnly)
            {
                _genericFieldsSettersCache[fieldName] = AccessorBuilder.BuildSetter<TInstance>(field);
            }
        }
    }

    /// <summary>
    /// 根据给定的对象和属性名称对属性进行读写操作
    /// </summary>
    public object this[TInstance instance, string propertyName]
    {
        get
        {
            if (_genericPropertiesGettersCache[propertyName] is Func<TInstance, object> getter)
            {
                return getter(instance);
            }
            else if (_genericFieldsGettersCache[propertyName] is Func<TInstance, object> getter2)
            {
                return getter2(instance);
            }
            throw new ArgumentException($"Type: \"{instance.GetType().FullName}\" does not have a property or field named: \"{propertyName}\" that supports reading.");
        }

        set
        {
            if (_genericPropertiesSettersCache[propertyName] is Action<TInstance, object> setter)
            {
                setter(instance, value);
            }
            else if (_genericFieldsSettersCache[propertyName] is Action<TInstance, object> setter2)
            {
                setter2(instance, value);
            }
            else
            {
                throw new ArgumentException($"Type: \"{instance.GetType().FullName}\" does not have a property or field named: \"{propertyName}\" that supports writing.");
            }
        }
    }

    /// <summary>
    /// 根据给定的对象和属性名称对属性进行读写操作
    /// </summary>
    public List<object> GetValues(IEnumerable<TInstance> instances, string propertyName)
    {
        if (instances.IsNullOrEmpty()) return null;
        if (_genericPropertiesGettersCache[propertyName] is Func<TInstance, object> getter)
        {
            return instances.Select(i => getter(i)).ToList();
        }
        else if (_genericFieldsGettersCache[propertyName] is Func<TInstance, object> getter2)
        {
            return instances.Select(i => getter2(i)).ToList();
        }
        throw new ArgumentException($"Type: \"{instances.First().GetType().FullName}\" does not have a property or field named: \"{propertyName}\" that supports reading.");
    }

    /// <summary>
    /// 根据给定的对象和属性名称对属性进行读写操作
    /// </summary>
    public List<List<(string propertyName, object value)>> GetValuesMulti(IEnumerable<TInstance> instances, List<string> propertyNames)
    {
        if (instances.IsNullOrEmpty()) return null;
        var _instances = instances.SpeculativeToArray();
        var getters = propertyNames.Select(propertyName =>
         {
             if (_genericPropertiesGettersCache[propertyName] is Func<TInstance, object> getter) return getter;
             else if (_genericFieldsGettersCache[propertyName] is Func<TInstance, object> getter2) return getter2;
             throw new ArgumentException($"Type: \"{instances.First().GetType().FullName}\" does not have a property or field named: \"{propertyName}\" that supports reading.");
         }).ToArray();
        var ret = new List<List<(string propertyName, object value)>>(_instances.Length);
        foreach (var instance in _instances)
        {
            var row = new List<(string propertyName, object value)>();
            for (int i = 0; i < propertyNames.Count; i++)
            {
                var propertyName = propertyNames[i];
                var val = getters[i](instance);
                row.Add((propertyName, val));
            }
            ret.Add(row);
        }
        return ret;
    }

    /// <summary>
    /// 尝试获取指定对象的指定属性[字段]值
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public bool TryGet<TProperty>(TInstance instance, string propertyOrFieldName, out TProperty value)
    {
        var cache = _genericInstanceGettersCache;

        var getter = cache[propertyOrFieldName] as Func<TInstance, TProperty>;
        if (getter is null)
        {
            PropertyInfo prop = null;
            FieldInfo field = null;
            if (!Properties.TryGetValue(propertyOrFieldName, out prop) && !Fields.TryGetValue(propertyOrFieldName, out field))
            {
                value = default;
                return false;
            }

            lock (cache)
            {
                getter = cache[propertyOrFieldName] as Func<TInstance, TProperty>;
                if (getter is null)
                {
                    try
                    {
                        if (prop != null)
                        {
                            getter = AccessorBuilder.BuildGetter<TInstance, TProperty>(
                                prop.Name, IncludesNonPublic);
                            cache[prop.Name] = getter;
                        }
                        else if (field != null)
                        {
                            getter = AccessorBuilder.BuildFieldGetter<TInstance, TProperty>(
                                field.Name, IncludesNonPublic);
                            cache[field.Name] = getter;
                        }
                    }
                    catch (ArgumentException)
                    {
                        value = default;
                        return false;
                    }
                }
            }
        }

        value = getter(instance);
        return true;
    }

    /// <summary>
    /// 尝试设置指定对象的指定属性值
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public bool TrySet<TProperty>(TInstance instance, string propertyName, TProperty value)
    {
        var cache = _genericInstanceSettersCache;

        var setter = cache[propertyName] as Action<TInstance, TProperty>;
        if (setter is null)
        {
            PropertyInfo prop;
            FieldInfo field = null;
            if (!Properties.TryGetValue(propertyName, out prop) && !Fields.TryGetValue(propertyName, out field)) { return false; }

            lock (cache)
            {
                setter = cache[propertyName] as Action<TInstance, TProperty>;
                if (setter is null)
                {
                    try
                    {
                        if (prop != null)
                        {
                            setter = AccessorBuilder.BuildSetter<TInstance, TProperty>(
                            prop.Name, IncludesNonPublic);
                            cache[prop.Name] = setter;
                        }
                        else if (field != null)
                        {
                            setter = AccessorBuilder.BuildFieldSetter<TInstance, TProperty>(
                            field.Name, IncludesNonPublic);
                            cache[field.Name] = setter;
                        }
                    }
                    catch (ArgumentException)
                    {
                        return false;
                    }
                }
            }
        }

        setter(instance, value);
        return true;
    }
}